package com.cheyunkeji.er.view;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.OverScroller;


/**
 * 自定义可回弹ScrollView
 *
 * @author wangyajun
 */
public class SScrollView extends FrameLayout {
//    private static final LogUtil log = new LogUtil("SScrollView");
    /**
     * children根节点
     */
    private View mChild;
    /**
     * 当前滚动y坐标
     */
    private int scrollY = 0;
    /**
     * 阻尼系数,越小阻力就越大(比如手指滑动100像素，view滑动100*RATE)
     */
    private static final float RATE = 0.6f;
    /**
     * 速度跟踪器
     */
    private VelocityTracker mVelocityTracker;
    /**
     * 第一次按下时y坐标
     */
    private float yDown;
    /**
     * 手指移动时的y坐标
     */
    private float yMove;
    /**
     * 无状态
     */
    private static final int STATE_NONE = 0;
    /**
     * 纵向拖动
     */
    private static final int STATE_Y = 1;
    /**
     * 下拉回弹
     */
    private static final int STATE_UP = 2;
    /**
     * 上拉回弹
     */
    private static final int STATE_DOWN = 4;
    /**
     * 松手滑动
     */
    private static final int STATE_FLING = 8;
    /**
     * 当前状态
     */
    private int state = STATE_NONE;
    /**
     * True if the user is currently dragging this ScrollView around. This is
     * not the same as 'is being flinged', which can be checked by
     * mScroller.isFinished() (flinging begins when the user lifts his finger).
     */
    private OverScroller mScroller;
    private int mTouchSlop;
    private int mMinimumVelocity;
    private int mMaximumVelocity;

    private int mPaddingLeft = 0;
    private int mPaddingRight = 0;
    private int mPaddingTop = 0;
    private int mPaddingBottom = 0;

    public SScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.scrollViewStyle);
    }

    public SScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        mScroller = new OverScroller(getContext(), new DecelerateInterpolator());
        setFocusable(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        setWillNotDraw(false);
        // 滚动条隐藏
        setVerticalScrollBarEnabled(false);
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (null == mChild && 1 == getChildCount()) {
            mChild = getChildAt(0);
            // 用以获得焦点
            mChild.setClickable(true);
        }
    }

    /**
     * 焦点位置
     */
    private Rect mTempRect = new Rect();

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (0 == oldw && 0 == oldh)
            return;
        View currentFocused = findFocus();
        if (null == currentFocused || this == currentFocused)
            return;
        // If the currently-focused view was visible on the screen when the
        // screen was at the old height, then scroll the screen to make that
        // view visible with the new screen height.
        if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
            currentFocused.getDrawingRect(mTempRect);
            offsetDescendantRectToMyCoords(currentFocused, mTempRect);
            int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
            doScrollY(scrollDelta);
        } else {
            scrollTo(0, 0);
        }
    }

    /**
     * Smooth scroll by a Y delta
     *
     * @param delta the number of pixels to scroll by on the Y axis
     */
    private void doScrollY(int delta) {
        if (delta != 0) {
            scrollBy(0, delta);
        } else {
            scrollY = getScrollY();
            int offset = getOffset();

            if (scrollY < 0 || offset <= 0) {
                // scrollY < 0 或者 child - parent高度差<=0
                scrollBackTop();
            } else if (scrollY >= offset) {
                // scrollY > 高度差
                scrollBackBottom();
            }
        }
    }

    /**
     * Compute the amount to scroll in the Y direction in order to get a
     * rectangle completely on the screen (or, if taller than the screen, at
     * least the first screen size chunk of it).
     *
     * @param rect The rect.
     * @return The scroll delta.
     */
    protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
        if (getChildCount() == 0)
            return 0;

        int height = getHeight();
        int screenTop = getScrollY();
        int screenBottom = screenTop + height;

        int fadingEdge = getVerticalFadingEdgeLength();

        // leave room for top fading edge as long as rect isn't at very top
        if (rect.top > 0) {
            screenTop += fadingEdge;
        }

        // leave room for bottom fading edge as long as rect isn't at very
        // bottom
        if (rect.bottom < getChildAt(0).getHeight()) {
            screenBottom -= fadingEdge;
        }

        int scrollYDelta = 0;

        if (rect.bottom > screenBottom && rect.top > screenTop) {
            // need to move down to get it in view: move down just enough so
            // that the entire rectangle is in view (or at least the first
            // screen size chunk).

            if (rect.height() > height) {
                // just enough to get screen size chunk on
                scrollYDelta += (rect.top - screenTop);
            } else {
                // get entire rect at bottom of screen
                scrollYDelta += (rect.bottom - screenBottom);
            }

            // make sure we aren't scrolling beyond the end of our content
            int bottom = getChildAt(0).getBottom();
            int distanceToBottom = bottom - screenBottom;
            scrollYDelta = Math.min(scrollYDelta, distanceToBottom);

        } else if (rect.top < screenTop && rect.bottom < screenBottom) {
            // need to move up to get it in view: move up just enough so that
            // entire rectangle is in view (or at least the first screen
            // size chunk of it).

            if (rect.height() > height) {
                // screen size chunk
                scrollYDelta -= (screenBottom - rect.bottom);
            } else {
                // entire rect at top
                scrollYDelta -= (screenTop - rect.top);
            }

            // make sure we aren't scrolling any further than the top our
            // content
            scrollYDelta = Math.max(scrollYDelta, -getScrollY());
        }
        return scrollYDelta;
    }

    /**
     * @return whether the descendant of this scroll view is within delta pixels
     * of being on the screen.
     */
    private boolean isWithinDeltaOfScreen(View descendant, int delta, int height) {
        descendant.getDrawingRect(mTempRect);
        offsetDescendantRectToMyCoords(descendant, mTempRect);

        return (mTempRect.bottom + delta) >= getScrollY() && (mTempRect.top - delta) <= (getScrollY() + height);
    }

    /**
     * <p>
     * The scroll range of a scroll view is the overall height of all of its
     * children.
     * </p>
     */
    @Override
    protected int computeVerticalScrollRange() {
        final int count = getChildCount();
        final int contentHeight = getHeight() - 0 - 0;
        if (count == 0) {
            return contentHeight;
        }

        int scrollRange = getChildAt(0).getBottom();
        final int scrollY = getScrollY();
        final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
        if (scrollY < 0) {
            scrollRange -= scrollY;
        } else if (scrollY > overscrollBottom) {
            scrollRange += scrollY - overscrollBottom;
        }

        return scrollRange;
    }

    @Override
    protected int computeVerticalScrollOffset() {
        return Math.max(0, super.computeVerticalScrollOffset());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (getChildCount() > 0) {
            final View child = getChildAt(0);
            int height = getMeasuredHeight();
            mPaddingLeft = getPaddingLeft();
            mPaddingRight = getPaddingRight();
            mPaddingTop = getPaddingTop();
            mPaddingBottom = getPaddingBottom();
            if (child.getMeasuredHeight() < height) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, mPaddingLeft + mPaddingRight,
                        lp.width);
                height -= mPaddingTop;
                height -= mPaddingBottom;
                int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);

                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

    @Override
    protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
        ViewGroup.LayoutParams lp = child.getLayoutParams();

        int childWidthMeasureSpec;
        int childHeightMeasureSpec;

        childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);

        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    @Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
                                           int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight
                + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin,
                MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        boolean b = false;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                if (null != mScroller && !mScroller.isFinished() && getOffset() > 0 && STATE_FLING == (state & STATE_FLING)) {
                    mScroller.abortAnimation();
                }
                state = STATE_NONE;
                yDown = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                yMove = ev.getY();
                final int deltaY = (int) (yMove - yDown);
                if (Math.abs(deltaY) >= mTouchSlop && STATE_NONE == state) {
                    // 纵向拖动
                    state = STATE_Y;
                    b = true;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (STATE_Y == (state & STATE_Y))
                    b = true;
                break;
        }
        return b;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        createVelocityTracker(ev);
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                return false;
            case MotionEvent.ACTION_MOVE:
                float nowY = ev.getY();
                int deltaY = (int) (yDown - nowY);
                // log.d("ACTION_MOVE", "nowY=" + nowY);
                yDown = nowY;
                scrollBy(0, (int) (deltaY * RATE));
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (getScrollY() != 0) {
                    touchUp();
                }
                recycleVelocityTracker();
                break;
        }
        return super.onTouchEvent(ev);
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            int currY = mScroller.getCurrY();
            scrollTo(0, currY);
            postInvalidate();
        }
    }

    /**
     * 取得child - parent的高度差
     *
     * @return
     */
    private int getOffset() {
        int offset = 0;
        if (null == mChild)
            return offset;
        int childH = mChild.getMeasuredHeight();
        int parentH = getMeasuredHeight();
        offset = childH - parentH;
        return offset;
    }

    /**
     * 创建VelocityTracker对象
     *
     * @param event content界面的滑动事件
     */
    private void createVelocityTracker(MotionEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
    }

    /**
     * 释放VelocityTracker对象
     */
    private void recycleVelocityTracker() {
        if (null != mVelocityTracker) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    /**
     * 获取手指在纵向的滑动速度
     *
     * @return 滑动速度，以每秒钟移动了多少像素值为单位。
     */
    private int getScrollVelocity() {
        if (null == mVelocityTracker)
            return 0;
        mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
        int velocity = (int) mVelocityTracker.getYVelocity();
        return velocity;
    }

    /**
     * 滚动到child顶部
     */
    public void scrollToTop() {
        scrollTo(0, 0);
    }

    /**
     * 滚动到child底部
     */
    public void scrollToBottom() {
        int offset = getOffset();
        if (offset > 0) {
            int mScrollY = getScrollY();
            int dy = offset - mScrollY;
            mScroller.startScroll(0,// 起始x坐标
                    mScrollY, // 起始y坐标
                    0, // 滚动x距离（正：向左，负:向右）
                    dy, // 滚动y距离（正：向上，负：向下）
                    300); // 持续时间
            postInvalidate();
        } else {
            scrollToTop();
        }
    }

    /**
     * 手指抬起后的操作
     */
    private void touchUp() {
        scrollY = getScrollY();
        int offset = getOffset();
        if (scrollY <= offset && scrollY >= 0) {
            int v = getScrollVelocity();
            // 惯性滑动
            if ((Math.abs(v) > mMinimumVelocity)) {
                fling(-v);
            }
            return;
        }

        if (scrollY < 0 || offset <= 0) {
            // scrollY < 0 或者 child - parent高度差<=0
            scrollBackTop();
        } else if (scrollY >= offset) {
            // scrollY > 高度差
            scrollBackBottom();
        }
    }

    /**
     * Fling the scroll view
     *
     * @param velocityY The initial velocity in the Y direction. Positive numbers mean
     *                  that the finger/cursor is moving down the screen, which means
     *                  we want to scroll towards the top.
     */
    public void fling(int velocityY) {
        state |= STATE_FLING;
        int height = getHeight();
        int bottom = mChild.getHeight();

        mScroller.fling(0, getScrollY(), 0, velocityY, 0, 0, 0, Math.max(0, bottom - height), 0, 20);
        invalidate();
    }

    /**
     * 回滚到顶端默认位置
     */
    private void scrollBackTop() {
        state |= STATE_UP;
        int mScrollY = getScrollY();
        mScroller.startScroll(0,// 起始x坐标
                mScrollY, // 起始y坐标
                0, // 滚动x距离（正：向左，负:向右）
                -mScrollY, // 滚动y距离（正：向上，负：向下）
                300); // 持续时间
        postInvalidate();
    }

    /**
     * 回滚到底端默认位置
     */
    private void scrollBackBottom() {
        state |= STATE_DOWN;
        int mScrollY = getScrollY();
        int offset = getOffset();
        int dy = Math.abs(mScrollY - offset);
        if (0 != mScrollY)
            dy = -dy;
        mScroller.startScroll(0,// 起始x坐标
                mScrollY, // 起始y坐标
                0, // 滚动x距离（正：向左，负:向右）
                dy, // 滚动y距离（正：向上，负：向下）
                300); // 持续时间
        postInvalidate();
    }

    @Override
    public void addView(View child) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child);
    }

    @Override
    public void addView(View child, int index) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, index);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, params);
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, index, params);
    }

    //

    private ScrollViewListener scrollViewListener = null;

    public void setScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }

    public interface ScrollViewListener {
        void onScrollChanged(SScrollView scrollView, int x, int y, int oldx, int oldy);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (scrollViewListener != null) {
            scrollViewListener.onScrollChanged(this, l, t, oldl, oldt);
        }
    }
}
